package com.gitee.wsl.mathematics.algebraic.number

import com.gitee.wsl.mathematics.algebraic.Algebra
import kotlin.math.E
import kotlin.math.PI

/**
 * An algebraic structure where elements can have numeric representation.
 *
 * @param T the type of element of this structure.
 */
 interface NumericAlgebra<T> : Algebra<T> {
    /**
     * Wraps a number to [T] object.
     *
     * @param value the number to wrap.
     * @return an object.
     */
     fun number(value: Number): T

    /**
     * Dynamically dispatches a binary operation with the certain name with numeric first argument.
     *
     * Implementations must fulfil the following requirements:
     *
     * 1. If operation is not defined in the structure, then function throws [kotlin.IllegalStateException].
     * 1. Equivalence to [leftSideNumberOperation]: for any `a`, `b`, and `c`,
     * `leftSideNumberOperationFunction(a)(b, c) == leftSideNumberOperation(a, b)`.
     *
     * @param operation the name of operation.
     * @return an operation.
     */
     fun leftSideNumberOperationFunction(operation: String): (left: Number, right: T) -> T =
        { l, r -> binaryOperationFunction(operation)(number(l), r) }

    /**
     * Dynamically invokes a binary operation with the certain name with numeric first argument.
     *
     * Implementations must fulfil the following requirements:
     *
     * 1. If operation is not defined in the structure, then the function throws [kotlin.IllegalStateException].
     * 1. Equivalence to [leftSideNumberOperation]: for any `a`, `b`, and `c`,
     * `leftSideNumberOperationFunction(a)(b, c) == leftSideNumberOperation(a, b, c)`.
     *
     * @param operation the name of operation.
     * @param left the first argument of operation.
     * @param right the second argument of operation.
     * @return a result of operation.
     */
     fun leftSideNumberOperation(operation: String, left: Number, right: T): T =
        leftSideNumberOperationFunction(operation)(left, right)

    /**
     * Dynamically dispatches a binary operation with the certain name with numeric first argument.
     *
     * Implementations must fulfil the following requirements:
     *
     * 1. If operation is not defined in the structure, then the function throws [kotlin.IllegalStateException].
     * 1. Equivalence to [rightSideNumberOperation]: for any `a`, `b`, and `c`,
     * `rightSideNumberOperationFunction(a)(b, c) == leftSideNumberOperation(a, b, c)`.
     *
     * @param operation the name of operation.
     * @return an operation.
     */
     fun rightSideNumberOperationFunction(operation: String): (left: T, right: Number) -> T =
        { l, r -> binaryOperationFunction(operation)(l, number(r)) }

    /**
     * Dynamically invokes a binary operation with the certain name with numeric second argument.
     *
     * Implementations must fulfil the following requirements:
     *
     * 1. If operation is not defined in the structure, then the function throws [kotlin.IllegalStateException].
     * 1. Equivalence to [rightSideNumberOperationFunction]: for any `a`, `b`, and `c`,
     * `rightSideNumberOperationFunction(a)(b, c) == rightSideNumberOperation(a, b, c)`.
     *
     * @param operation the name of operation.
     * @param left the first argument of operation.
     * @param right the second argument of operation.
     * @return a result of operation.
     */
     fun rightSideNumberOperation(operation: String, left: T, right: Number): T =
        rightSideNumberOperationFunction(operation)(left, right)

    override fun bindSymbolOrNull(value: String): T? = when (value) {
        "pi" -> number(PI)
        "e" -> number(E)
        else -> super.bindSymbolOrNull(value)
    }
}

/**
 * The &pi; mathematical constant.
 */
 val <T> NumericAlgebra<T>.pi: T get() = bindSymbolOrNull("pi") ?: number(PI)

/**
 * The *e* mathematical constant.
 */
 val <T> NumericAlgebra<T>.e: T get() = number(E)

